package com.fg.core;

import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.annotation.IEnum;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.TableNameParser;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.fg.exception.NormalTerminationException;
import com.fg.utils.ToolsThreadContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.stereotype.Component;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author: 王富贵
 * @description: sql输出拦截器，用于动态表明替换和占位符替换，并输出sql
 * @createTime: 2024/4/21 8:58
 */
@Slf4j
@Component
public class ReplacePlaceholderInnerInterceptor implements InnerInterceptor {
    /**
     * 拦截所有sql
     *
     * @param sh                 StatementHandler(可能是代理对象)
     * @param connection         Connection
     * @param transactionTimeout transactionTimeout
     */
    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
        MappedStatement mappedStatement = mpSh.mappedStatement();
        final BoundSql boundSql = mpSh.boundSql();

        // 动态表名
        if (StringUtils.isNotBlank(ToolsThreadContext.get().getDynamicTableName())) {
            PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
            mpBs.sql(this.changeTable(mpBs.sql()));
        }
        // 占位符替换
        String resultSQL = this.replacePlaceholder(mappedStatement, boundSql);
        resultSQL += ";";
        log.info("SQL: {}", resultSQL);
        ToolsThreadContext.get().secureGetFileWrite().write(resultSQL);
        // 用于打断真正执行sql的异常
        throw new NormalTerminationException();
    }

    /**
     * 替换占位符
     *
     * @param mappedStatement
     * @param boundSql
     * @return sql
     */
    protected String replacePlaceholder(MappedStatement mappedStatement, BoundSql boundSql) {
        // sql格式化
        String sql = boundSql.getSql().replaceAll("\\s+", " ");
        // .toLowerCase();
        // 驼峰转换
        // this.convertToCamelCase(sql);
        StringBuilder resultSqlBuild = null;
        log.info("sourceSQL: " + sql);
        Object parameterObject = boundSql.getParameterObject();
        // 获取占位参数
        List<ParameterMapping> parameterMappings = new ArrayList<>(boundSql.getParameterMappings());
        Configuration configuration = mappedStatement.getConfiguration();
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();

        try {
            String parameter = "null";
            MetaObject newMetaObject = configuration.newMetaObject(parameterObject);
            // 参数数组
            ArrayList<String> parameters = new ArrayList<>();
            for (ParameterMapping parameterMapping : parameterMappings) {
                if (parameterMapping.getMode() == ParameterMode.OUT) {
                    continue;
                }
                String propertyName = parameterMapping.getProperty();
                if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    parameter = this.getParameterValue(parameterObject);
                } else if (newMetaObject.hasGetter(propertyName)) {
                    parameter = this.getParameterValue(newMetaObject.getValue(propertyName));
                } else if (boundSql.hasAdditionalParameter(propertyName)) {
                    parameter = this.getParameterValue(boundSql.getAdditionalParameter(propertyName));
                }
                // 加入参数数组
                parameters.add(parameter);

                // fixme 此处不严谨，若sql语句中有?，则替换错位。?️
                // sql = sql.replaceFirst("\\?", parameter);
            }
            log.info("params: {}", parameters);

            /**
             * 占位符填充
             * 保证后续插入的？不被错误填充
             * 源：update tools set name=? where (id in (?,?,?,?,?))
             * 插入参数：富??贵,0,1,2,3,4
             * 目标：update tools set name='富??贵' where (id in (0,1,2,3,4))
             * 遍历数组获取并记录到第一个?的位置index，插入参数后index加上插入的字符串，从这个位置之后再找？就不会出现插入字符串问号被读到的问题
             */
            int index = sql.indexOf('?');
            resultSqlBuild = new StringBuilder(sql);
            for (String parameterTmp : parameters) {
                // 找不到问号直接跳出循环
                if (index == -1) {
                    break;
                }
                // 替换?这串的字符，因为?是一位的，所以是+1
                resultSqlBuild.replace(index, index + 1, parameterTmp);
                // 标识移动到插入新参数之后
                index += parameterTmp.length();
                // 插入新参数之后再找？
                index = resultSqlBuild.indexOf("?", index);
            }
        } catch (Exception e) {
            log.error(String.format("intercept sql error: [%s]", sql), e);
        }
        return resultSqlBuild.toString();
    }

    /**
     * 表名替换方法，支持多表名替换
     *
     * @param sql
     * @return
     */
    protected String changeTable(String sql) {
        // 表名解析器
        TableNameParser parser = new TableNameParser(sql);
        // 创建解析器解析出来的表名列表，列表的愿意是多sql语句执行
        List<TableNameParser.SqlToken> names = new ArrayList<>();
        // 将解析的表名添加进列表中
        parser.accept(names::add);
        // 创建一个最终sql存放string
        StringBuilder builder = new StringBuilder();
        // 记录上一个表名结束的位置
        int last = 0;
        // 遍历替换表名
        for (TableNameParser.SqlToken name : names) {
            // 当前表名在sql语句中的起始位置
            int start = name.getStart();
            if (start != last) {
                // 将当前表名之前的 SQL 片段添加到新的 SQL 语句中 如：select * from
                builder.append(sql, last, start);
                // 添加表名
                builder.append(ToolsThreadContext.get().getDynamicTableName());
            }
            // 更新 last 变量为当前表名的结束位置，以便下一次循环使用
            last = name.getEnd();
        }
        // 如果最后一个表名之后还存在其他的 SQL 片段，则将这部分 SQL 片段添加到新的 SQL 语句中
        if (last != sql.length()) {
            builder.append(sql.substring(last));
        }
        return builder.toString();
    }

    /**
     * 获取参数
     *
     * @param param Object类型参数
     * @return 转换之后的参数
     */
    protected String getParameterValue(Object param) {
        if (param == null) {
            return "null";
        }
        if (param instanceof Number) {
            return param.toString();
        }
        String value = null;
        if (param instanceof String) {
            value = param.toString();
        } else if (param instanceof Date) {
            DateUtil.format((Date) param, "yyyy-MM-dd HH:mm:ss");
        } else if (param instanceof IEnum) {
            value = java.lang.String.valueOf(((IEnum) param).getValue());
        } else {
            value = param.toString();
        }
        return StringUtils.quotaMark(value);
    }

    /**
     * 驼峰转换工具
     * tableName = table_name
     *
     * @param parameter
     * @return
     */
    protected String convertToCamelCase(String parameter) {
        StringBuilder result = new StringBuilder();

        for (int i = 0; i < parameter.length(); i++) {
            char currentChar = parameter.charAt(i);

            if (Character.isUpperCase(currentChar)) {
                // 如果是大写字母，转换为小写字母并在前面添加下划线
                result.append("_").append(Character.toLowerCase(currentChar));
            } else {
                // 如果不是大写字母，直接添加到结果中
                result.append(currentChar);
            }
        }
        return result.toString();
    }
}
